#include "money-tree.hpp"
#include "dirs.hpp"
#include "opportunity.hpp"
#include "params.hpp"
#include "strategy-00.hpp"
#include "strategy.hpp"
#include "tick.h"
#include "tjm_action.hpp"
#include "trigger.hpp"
#include "types.h"
#include "udp.h"
#include <cstdint>
#include <cstring>
#include <exception>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>

UdpClient* g_uc = nullptr;
price_t g_hop = 12.0;
int debug_level = 0;
#define LARGE_OPPORTUNITY 68

namespace Trade {

void
send_reverse_space(const time_t tm,
                   const price_t delta,
                   const Lines bl) noexcept
{
  const char* format =
    R"({"t":16,"i":%ld,"rd":%.01f,"nu":%d,"u1":%.01f,"u2":%.02f,"nd":%d,"d1":%.01f,"d2":%.02f})";
  char buf[1024];
  snprintf(buf,
           1023,
           format,
           tm,
           delta,
           bl.nup,
           bl.hp_up_1,
           bl.hp_up_2,
           bl.ndn,
           bl.hp_dn_1,
           bl.hp_dn_2);
  udpc_send_message(g_uc, buf);
}

void
send_action(const int64_t idx,
            const time_t tm,
            const int who,
            const int action,
            const dir_t d,
            const price_t p,
            const volume_t v) noexcept
{
  if (debug_level != DEBUG_LEVEL_NONE) {
    if (p < 0) {
      do_something();
      return;
    }
    static const char* format = "{\"t\":15,\"i\":%ld,\"d\":%d,\"p\":%.02lf,"
                                "\"tm\":%ld,\"v\":%d,\"a\":%d,\"w\":%d}";
    char buf[1024];
    snprintf(buf, 1023, format, idx, d, p, tm, v, action, who);
    udpc_send_message(g_uc, buf);
  }
}

MoneyTree::MoneyTree() noexcept
{
  tjm.mockif.on_open = std::bind(
    &MoneyTree::on_open, this, std::placeholders::_1, std::placeholders::_2);
  tjm.mockif.on_close = std::bind(
    &MoneyTree::on_close, this, std::placeholders::_1, std::placeholders::_2);
}

MoneyTree::~MoneyTree() noexcept {}

void
MoneyTree::set_strategy(const int strategy_id) noexcept
{
  if (strategy) {
    delete strategy;
    strategy = nullptr;
  }

  if (strategy_id == 0)
    strategy = new Strategy00(this, &tjm);

  if (strategy)
    bind_call_back_functions();
}

void
MoneyTree::bind_call_back_functions()
{
  if (strategy) {
    triggers.get_trigger(TRIGGER_EVENT_TYPE::CLOSE, TRIGGER_TYPE::SPACE)
      ->set_call_back(
        std::bind(&Strategy::on_close, strategy, std::placeholders::_1));
    triggers
      .get_trigger(TRIGGER_EVENT_TYPE::CLOSE_STOP_LOSS, TRIGGER_TYPE::SPACE)
      ->set_call_back(std::bind(
        &Strategy::on_close_stop_loss, strategy, std::placeholders::_1));
    triggers
      .get_trigger(TRIGGER_EVENT_TYPE::CLOSE_STOP_WIN, TRIGGER_TYPE::SPACE)
      ->set_call_back(std::bind(
        &Strategy::on_close_stop_win, strategy, std::placeholders::_1));
    triggers
      .get_trigger(TRIGGER_EVENT_TYPE::CLOSE_MIN_WIN, TRIGGER_TYPE::SPACE)
      ->set_call_back(std::bind(
        &Strategy::on_close_min_win, strategy, std::placeholders::_1));
    triggers.get_trigger(TRIGGER_EVENT_TYPE::OPEN, TRIGGER_TYPE::SPACE)
      ->set_call_back(
        std::bind(&Strategy::on_open, strategy, std::placeholders::_1));
    triggers
      .get_trigger(TRIGGER_EVENT_TYPE::MARK_PRICE_UP, TRIGGER_TYPE::SPACE)
      ->set_call_back(std::bind(
        &Strategy::on_mark_price_up, strategy, std::placeholders::_1));
    triggers
      .get_trigger(TRIGGER_EVENT_TYPE::MARK_PRICE_DN, TRIGGER_TYPE::SPACE)
      ->set_call_back(std::bind(
        &Strategy::on_mark_price_dn, strategy, std::placeholders::_1));
    triggers.get_trigger(TRIGGER_EVENT_TYPE::MARK_TIMER, TRIGGER_TYPE::TIME)
      ->set_call_back(std::bind(
        &Strategy::on_mark_timer_out, strategy, std::placeholders::_1));
  }
}

static int64_t g_pause_idx = 0;

int
MoneyTree::debug_show() noexcept
{
  if (((mt_event & EVENT_CE_DIR_CHANGED) && !g_pause_idx) ||
      (g_pause_idx == idx)) {
    if (g_pause_idx)
      g_pause_idx = 0;
    if (debug_level == DEBUG_STEP_BY_STEP) {
      return step_by_step();
    } else if (debug_level == DEBUG_SHOW_DATA) {
      show_something();
      do_something();
    }
  }
  return (0);
}

#define DEBUG_SHOW                                                             \
  {                                                                            \
    ret = debug_show();                                                        \
    if (ret < 0)                                                               \
      break;                                                                   \
  }

int
MoneyTree::make_deal(const int64_t start_idx) noexcept
{
  if (!strategy) {
    printf("No strategy\n");
    return (-10000);
  }

  // clean ui
  send_action(0, 0, 0, 0, 0, 0, 0);
  tjm.send_timer();

#if (STONE_DEBUG)
  int64_t debug_idx = 4618988;
#endif

  int ret = 0;
  bool start = false;
  for (idx = start_idx; idx <= vom.eidx; ++idx) {
    t_now = time_t(td[idx].ftime);
    hp_now = td[idx].price;

#if (STONE_DEBUG)
    if (idx == debug_idx) {
      do_something();
    }
#endif

    vo = vom.get_data_by_index(idx);
    if (!vo) {
      printf("Err, can not get var and obj at %ld\n", idx);
      ret = -1;
      break;
    }
    tjm.set_data(vo, hp_now);

    if (!start) {
      if (vo->oppor_trend.good()) {
        start = true;
      } else {
        continue;
      }
    }

    mock_trade();

    mt_event = 0;

    if (!tm_near.t && vo->nft.get_solved_near_time()) {
      time_t t1, t2;
      vo->nft.get_solved_dir_time(d_tm_near, d_tm_far, t1, t2);
      if (t1)
        tm_near.set_timer(t1);
      if (t2)
        tm_far.set_timer(t2);
    }

    if (tm_near.approximately_time_out(t_now)) {
      // mt_event |= EVENT_PREDICTED_NEAR_TIME_OUT;
    }

    if (tm_far.approximately_time_out(t_now)) {
      // mt_event |= EVENT_PREDICTED_FAR_TIME_OUT;
    }

    if (vo->event &
        (EVENT_PREDICTED_NEAR_DIR_CHANGED | EVENT_PREDICTED_FAR_DIR_CHANGED)) {
      // save old
      tm_old_near = tm_near;
      tm_old_far = tm_far;

      time_t t1, t2;
      vo->nft.get_solved_dir_time(d_tm_near, d_tm_far, t1, t2);
      if (t1)
        tm_near.set_timer(t1);
      if (t2)
        tm_far.set_timer(t2);
    }

    // mt_event |= mt_tjm_reached();
    mt_tjm_reached();

    if (vo->event & EVENT_BIG_NEEDLE_OCCURED) {
      mt_event |= (EVENT_BIG_NEEDLE_OCCURED | EVENT_LINES_CHANGED);
    }

    if (vo->event & EVENT_LINES_CHANGED) {
      mt_event |= EVENT_LINES_CHANGED;
    }
    if (vo->event & EVENT_OPPORTUNITY_DIR_CHANGED) {
      mt_event |= EVENT_FILTER_EVENT;
    }

    dir_t dg = guess_dir();
    if (dg && dg != d_guess) {
      d_guess = dg;
      mt_event |= EVENT_CE_DIR_CHANGED;
    }

    dg = vo->flv_79.get_far_dir();
    if (dg && dg != d_far) {
      d_far = dg;
      mt_event |= EVENT_CE_DIR_CHANGED;
    }

    dg = vo->flv_79.get_near_dir();
    if (dg && dg != d_near) {
      d_near = dg;
      mt_event |= EVENT_CE_DIR_CHANGED;
    }

    if (reverse_space > 0) {
      // mt_event |= EVENT_FILTER_EVENT;
    }

    /*if (vo->event &
        (EVENT_SPACE_DIR_CHANGED | EVENT_SPACE_DIR_STABLE |
         EVENT_BIG_NEEDLE_DISAPPEARED | EVENT_BIG_REVERSE_OCCURED |
         EVENT_BIG_REVERSE_DISAPPEARED | EVENT_EXPLICIT_DIR_CHANGED |
         EVENT_FORCE_NEAR_DIR_CHANGED | EVENT_FORCE_FAR_DIR_CHANGED |
         EVENT_FORCE_NEAR_LEVEL_CHANGED | EVENT_FORCE_FAR_LEVEL_CHANGED |
         EVENT_SOLVED_NEAR_CHANGED | EVENT_SOLVED_FAR_CHANGED |
         EVENT_FORCE_RT_NEAR_DIR_CHANGED | EVENT_FORCE_RT_FAR_DIR_CHANGED |
         EVENT_FORCE_RT_NEAR_LEVEL_CHANGED | EVENT_FORCE_RT_FAR_LEVEL_CHANGED |
         EVENT_VISION_DIR_CHANGED | EVENT_INTENT_DIR_CHANGED |
         EVENT_VISION_WITH_SPACE |
       | EVENT_JERRY_REACHED_L2_FO)) {
      mt_event |= EVENT_FILTER_EVENT;
    }*/

    DEBUG_SHOW

    // Make Deal
    ret = mt_money_tree();

    if (ret) {
      // DEBUG_SHOW
    }

  } // for loop

  tjm.finish();

  tjm.print_all_deal(stdout);

  return ret;
}

#if (ONLINE_DATA_COMM)
int
MoneyTree::make_deal_online(const int64_t start_idx) noexcept
{
  if (!strategy) {
    printf("No strategy\n");
    return (-10000);
  }

  // clean ui
  send_action(0, 0, 0, 0, 0, 0, 0);
  tjm.send_timer();

#if (STONE_DEBUG)
  int64_t debug_idx = 4618988;
#endif

  int ret = 0;
  bool start = false;
  for (idx = start_idx; idx <= vom.eidx; ++idx) {
    t_now = time_t(td[idx].ftime);
    hp_now = td[idx].price;

#if (STONE_DEBUG)
    if (idx == debug_idx) {
      do_something();
    }
#endif

    vo = vom.get_data_by_index(idx);
    if (!vo) {
      printf("Err, can not get var and obj at %ld\n", idx);
      ret = -1;
      break;
    }
    tjm.set_data(vo, hp_now);

    if (!start) {
      if (vo->oppor_trend.good()) {
        start = true;
      } else {
        continue;
      }
    }

    mock_trade();

    mt_event = 0;

    if (!tm_near.t && vo->nft.get_solved_near_time()) {
      time_t t1, t2;
      vo->nft.get_solved_dir_time(d_tm_near, d_tm_far, t1, t2);
      if (t1)
        tm_near.set_timer(t1);
      if (t2)
        tm_far.set_timer(t2);
    }

    if (tm_near.approximately_time_out(t_now)) {
      // mt_event |= EVENT_PREDICTED_NEAR_TIME_OUT;
    }

    if (tm_far.approximately_time_out(t_now)) {
      // mt_event |= EVENT_PREDICTED_FAR_TIME_OUT;
    }

    if (vo->event &
        (EVENT_PREDICTED_NEAR_DIR_CHANGED | EVENT_PREDICTED_FAR_DIR_CHANGED)) {
      // save old
      tm_old_near = tm_near;
      tm_old_far = tm_far;

      time_t t1, t2;
      vo->nft.get_solved_dir_time(d_tm_near, d_tm_far, t1, t2);
      if (t1)
        tm_near.set_timer(t1);
      if (t2)
        tm_far.set_timer(t2);
    }

    // mt_event |= mt_tjm_reached();
    mt_tjm_reached();

    if (vo->event & EVENT_TJM_DIR_CHANGED) {
    }

    if (vo->event & EVENT_BIG_NEEDLE_OCCURED) {
      // mt_event |= EVENT_FILTER_EVENT;
    }

    if (vo->event & EVENT_LINES_CHANGED) {
      // if (!vo->better_lines.ndn && !vo->better_lines.nup) {
      // }
      // mt_event |= EVENT_FILTER_EVENT;
    }

    if (mt_event & EVENT_OPPO_OPPUR_CHANGED) {
    }

    if (vo->event & EVENT_LINES_CHANGED) {
    }

    DEBUG_SHOW

    // Make Deal
    int ret = mt_money_tree();

    if (ret) {
      // DEBUG_SHOW
    }

  } // for loop

  tjm.finish();

  tjm.print_all_deal(stdout);

  return ret;
}
#endif // ONLINE

void
MoneyTree::mock_trade() noexcept
{
  int v[4] = { 0, 0, 0, 0 };
  int vol = tjm.mockif.mock_deal(idx, hp_now, t_now, v);

  if (vol) {
#if (STONE_DEBUG || STEP_BY_STEP || ONLINE_DATA_COMM)
    tjm.td_show_revunue();
#endif
  }
}

event_t
MoneyTree::mt_tjm_reached() noexcept
{
  event_t mt_evt = 0;

  tjm.reached(mt_evt, vo->lines);
  tjm.lines_changed_();
  auto n = triggers.pull_trigger(hp_now, t_now);
  if (n) {
    do_something();
  }

  return mt_evt;
}

void
MoneyTree::show_something() const noexcept
{
  if (!debug_level)
    return;

  if (!g_pause_idx) {
    vo->send_cur_pos();
    tjm.send_timer();
  }

  vo->show_params(t_now, hp_now);
  printf("!!! ----- Guess %d ----- !!!\n", d_guess);

  // tjm.print();
  // print_reached("better", 0, vo->better_lines.reached, 0);
  // printf("Dynamic Dir = L1 %d, L2 %d\n", vo->d_dynamical, vo->d2_dynamical);
  fflush(stdout);
}

int
MoneyTree::step_by_step() noexcept
{
  show_something();

#if (STONE_DEBUG)
  // return (0);
#endif // STONE_DEBUG

  int ret = 0;
enter_something:
  std::cout << "Enter Command(" << idx << "): ";
  std::string str;
  std::cin >> str;
  if (str == "quit") {
    ret = -1;
    udpc_send_message(g_uc, "quit");
    goto exit_return;
  } else if (str == "p") {
    vo->send_cur_pos();
    tjm.send_timer();
    vo->show_params(t_now, hp_now);
    // force.print(hp_now);
    tjm.print();
  } else if (str == "r") {
    if (debug_level)
      tjm.td_show_revunue();
  } else if (str[0] == 'g' && str.length() > 1) {
    try {
      g_pause_idx = std::stoll(str.c_str() + 1);
      printf("Next stop is %ld\n", g_pause_idx);
    } catch (std::exception& e) {
      printf("%s\n", e.what());
    }
    goto exit_return;
  } else if (str[0] == 'f' && str.length() > 1) {
    try {
      g_pause_idx = std::stoll(str.c_str() + 1) * 60 + idx;
      printf("Next stop is %ld\n", g_pause_idx);
    } catch (std::exception& e) {
      printf("%s\n", e.what());
    }
    goto exit_return;
  } else if (str == "a") {
    g_pause_idx = 15 * 60 + idx;
    printf("Next stop is %ld\n", g_pause_idx);
    goto exit_return;
  } else if (str == "s") {
    g_pause_idx = 30 * 60 + idx;
    printf("Next stop is %ld\n", g_pause_idx);
    goto exit_return;
  } else if (str == "d") {
    g_pause_idx = 45 * 60 + idx;
    printf("Next stop is %ld\n", g_pause_idx);
    goto exit_return;
  } else if (str == "f") {
    g_pause_idx = 60 * 60 + idx;
    printf("Next stop is %ld\n", g_pause_idx);
    goto exit_return;
  } else if (str == "tu") {
    tjm.action_tom_run(1, -1, better_lines, 9997);
  } else if (str == "tn") {
    tjm.action_tom_run(-1, -1, better_lines, 9997);
  } else if (str == "ts") {
    tjm.action_tom_stop(-1, 9997);
  } else if (str == "ju") {
    tjm.action_jerry_run(1, better_lines, 9998);
  } else if (str == "jn") {
    tjm.action_jerry_run(-1, better_lines, 9998);
  } else if (str == "js") {
    tjm.action_jerry_stop(9998);
  } else if (str == "mu") {
    tjm.action_mickey_run(1, hp_now, better_lines, 9999);
  } else if (str == "mn") {
    tjm.action_mickey_run(-1, hp_now, better_lines, 9999);
  } else if (str == "ms") {
    tjm.action_mickey_stop(9999);
  } else if (str.length() > 1 && str[0] == 'o') {
    if (tjm.tom.is_running()) {
      if (str[1] == 'c') {
        tjm.mockif.mock_recall_all_open_order(tjm.tom.get_dir(), idx);
      } else {
        try {
          price_t hpx = hp_now - std::stof(str.c_str() + 1);
          tjm.mockif.mock_place_open_order(
            tjm.tom.get_dir(), hpx, hp_now, 1, t_now, idx);
        } catch (std::exception& e) {
        }
      }
    }
  } else if (str.length() > 1 && str[0] == 'c') {
    if (tjm.tom.is_running()) {
      if (str[1] == 'c') {
        tjm.mockif.mock_recall_all_close_order(tjm.tom.get_dir(), idx);
      } else {
        try {
          price_t hpx = hp_now - std::stof(str.c_str() + 1);
          tjm.mockif.mock_place_close_order(
            tjm.tom.get_dir(), hpx, hp_now, 1, t_now, idx);
        } catch (std::exception& e) {
        }
      }
    }
  } else if (str.length() > 1 && str[0] == 'x') {
    if (str[1] == 'a') {
      tjm.mockif.mock_recall_all_order(idx);
    }
  } else {
    goto exit_return;
  }
  goto enter_something;

exit_return:
  return ret;
}

void
MoneyTree::set_debug(const char* debug) noexcept
{
  if (!strcmp(debug, "debug"))
    debug_level = DEBUG_SHOW_DATA;
  else if (!strcmp(debug, "step"))
    debug_level = DEBUG_STEP_BY_STEP;
  else
    debug_level = DEBUG_LEVEL_NONE;
}

void
MoneyTree::set_data(const TICK_DATA td,
                    const size_t td_size,
                    const Params* vo,
                    const size_t vo_size) noexcept
{
  this->td = td;
  this->td_size = td_size;

  vom.set_mem_data(vo, vo_size);
}

int
MoneyTree::mt_money_tree() noexcept
{
  return strategy->money_tree();
}

int
MoneyTree::on_open(const OrderPtr& o, const DealPtr& deal) noexcept
{
  deal->print();
  strategy->on_order_open(o, deal);
  return (0);
}

int
MoneyTree::on_close(const OrderPtr& o, const DealPtr& deal) noexcept
{
  deal->print();
  strategy->on_order_close(o, deal);
  return (0);
}

dir_t
MoneyTree::guess_dir() noexcept
{
  dir_t d = 0;
  if (vo->event & EVENT_BIG_NEEDLE_OCCURED) {
    // d = -VO->dir_big_needle;
    if (-vo->dir_big_needle == vo->d_dynamical)
      d = vo->d_dynamical;
  } else {
    if (vo->d2_dynamical == vo->d_dynamical) {
      d = vo->d_dynamical;
    } else if (vo->dir_opportunity == vo->d_dynamical) {
      d = vo->d_dynamical;
    }
  }
  return d;
}

} // namespace Trade
